/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#include "BLPFile.h"

namespace PQGame
{
	#define COMPRESSED_RGB_S3TC_DXT1_EXT   0x83F0
	#define COMPRESSED_RGBA_S3TC_DXT1_EXT  0x83F1
	#define COMPRESSED_RGBA_S3TC_DXT3_EXT  0x83F2
	#define COMPRESSED_RGBA_S3TC_DXT5_EXT  0x83F3

	typedef struct  {
		unsigned char r, g, b;
	}rgb_color;

	void decompressDXTC(int format, int w, int h, int size, unsigned char *src, unsigned char *dest)
	{
		int bsx = (w<4) ? w : 4;
		int bsy = (h<4) ? h : 4;

		for(int y=0; y<h; y += bsy) {
			for(int x=0; x<w; x += bsx) {
				unsigned long long alpha = 0;
				Ogre::uint a0 = 0, a1 = 0;

				if (format == COMPRESSED_RGBA_S3TC_DXT3_EXT)	{
					alpha = *(unsigned long long*)src;
					src += 8;
				}

				Ogre::uint c0 = *(unsigned short*)(src + 0);
				Ogre::uint c1 = *(unsigned short*)(src + 2);
				src += 4;

				rgb_color color[4];
				color[0].b = (unsigned char) ((c0 >> 11) & 0x1f) << 3;
				color[0].g = (unsigned char) ((c0 >>  5) & 0x3f) << 2;
				color[0].r = (unsigned char) ((c0      ) & 0x1f) << 3;
				color[1].b = (unsigned char) ((c1 >> 11) & 0x1f) << 3;
				color[1].g = (unsigned char) ((c1 >>  5) & 0x3f) << 2;
				color[1].r = (unsigned char) ((c1      ) & 0x1f) << 3;
				if(c0 > c1) {
					color[2].r = (color[0].r * 2 + color[1].r) / 3;
					color[2].g = (color[0].g * 2 + color[1].g) / 3;
					color[2].b = (color[0].b * 2 + color[1].b) / 3;
					color[3].r = (color[0].r + color[1].r * 2) / 3;
					color[3].g = (color[0].g + color[1].g * 2) / 3;
					color[3].b = (color[0].b + color[1].b * 2) / 3;
				} else {
					color[2].r = (color[0].r + color[1].r) / 2;
					color[2].g = (color[0].g + color[1].g) / 2;
					color[2].b = (color[0].b + color[1].b) / 2;
					color[3].r = 0;
					color[3].g = 0;
					color[3].b = 0;
				}

				for (int j=0; j<bsy; j++) {
					Ogre::uint index = *src++;
					unsigned char* dd = dest + (w*(y+j)+x)*4;
					for (int i=0; i<bsx; i++) {
						*dd++ = color[index & 0x03].b;
						*dd++ = color[index & 0x03].g;
						*dd++ = color[index & 0x03].r;
						if (format == COMPRESSED_RGBA_S3TC_DXT1_EXT)	{
							*dd++ = ((index & 0x03) == 3 && c0 <= c1) ? 0 : 255;
						}
						else if (format == COMPRESSED_RGBA_S3TC_DXT3_EXT)	{
							*dd++ = (unsigned char)(alpha & 0x0f) << 4;
							alpha >>= 4;
						}
						index >>= 2;
					}
				}
			}
		}
	}

	BLPFile::~BLPFile(void)
	{
	}

	BLPFile::BLPFile(MPQFile* mpqFile)
		: _file(mpqFile),_mipsCount(16),_ok(false)
	{
		assert(_file);
		init();
	}

	void BLPFile::init()
	{
		_buf=(unsigned char*)_file->getBuffer();
		_header=(BLPHeader*)_buf;///// Always 'BLP2'
		if(strncmp(_header->id,"BLP2",4)!=0){
			_ok=false;
			return;
		}

		if (_header->type != 1) {
			//Type 0: JPEG compression 1: Uncompressed or DirectX compression
			return;
		}
		
		//Encoding 1: Uncompressed 2: Compression
		if (_header->encoding==1) {
			genUnCompressTex();
		}else if (_header->encoding == 2){
			genCompressTex();
		}

		_ok=true;
	}

	void BLPFile::genUnCompressTex()
	{
		_compressed=false;

		bool hasalpha = (_header->alphaDepth!=0);
		_mipsCount = (_header->hasMips>0) ? 16 : 1;

		int w=_header->width;
		int h=_header->height;

		for (int i=0; i<_mipsCount; i++) {
			if (w==0) w = 1;
			if (h==0) h = 1;
			if (_header->offsets[i] && _header->sizes[i]) {
				unsigned char *buf=_buf+_header->offsets[i];
				Ogre::uint *buf2 = new Ogre::uint[_header->width*_header->height];
				Ogre::uint *p = 0;
				unsigned char *c = 0, *a = 0;

				int cnt = 0;
				int alpha = 0;

				p = buf2;
				c = buf;
				a = buf + w*h;
				//process pixels
				for (int y=0; y<h; y++) {
					for (int x=0; x<w; x++) {
						Ogre::uint k = _header->palette[*c++];
						k = ((k&0x00FF0000)>>16) | ((k&0x0000FF00)) | ((k& 0x000000FF)<<16);

						if (hasalpha) {
							if (_header->alphaDepth == 8) {
								alpha = (*a++);
							} else if (_header->alphaDepth == 4) {
								alpha = (*a & (0xf << cnt++)) * 0x11;
								if (cnt == 2) {
									cnt = 0;
									a++;
								}
							} else if (_header->alphaDepth == 1) {
								alpha = (*a & (1 << cnt++)) ? 0xff : 0;
								if (cnt == 8) {
									cnt = 0;
									a++;
								}
							}
						} else{
							alpha = 0xff;
						}

						k |= alpha << 24;
						*p++ = k;
					}
				}
				_mipmap[i]=(unsigned char*)buf2;
				
			} else{
				break;
			}
			w >>= 1;
			h >>= 1;
		}
	}

	void BLPFile::genCompressTex()
	{
		_compressed=true;

		//decompressed buf
		unsigned char *ucbuf = new unsigned char[_header->width*_header->height*4];
		int format = COMPRESSED_RGBA_S3TC_DXT1_EXT;
		int blocksize = 8;

		/*AlphaDepth							AlphaEncoding
		0: No alpha channel					0: DXT1 alpha (0 or 1 bit alpha)
		1: 1 bit alpha								1: DXT2/3 alpha (4 bit alpha)
		4: 4 bit alpha (DXT3 only)			7: DXT4/5 alpha (interpolated alpha)
		8: 8 bit alpha
		*/
		if (_header->alphaDepth==8 || _header->alphaDepth==4) {
			format = COMPRESSED_RGBA_S3TC_DXT3_EXT;
			blocksize = 16;
		}

		if (_header->alphaDepth==8 && _header->alphaEncoding==7) {
			format = COMPRESSED_RGBA_S3TC_DXT5_EXT;
			blocksize = 16;
		}

		unsigned char *buf =0;
		_mipsCount = (_header->hasMips>0) ? 16 : 1;

		int w=_header->width;
		int h=_header->height;
		for (int i=0; i<_mipsCount; i++) {
			if (w==0) w = 1;
			if (h==0) h = 1;
			
			if (_header->offsets[i] && _header->sizes[i]) {
				buf= _buf+_header->offsets[i];

				int size = ((w+3)/4) * ((h+3)/4) * blocksize;
				decompressDXTC(format, w, h, size, buf, ucbuf);
				_mipmap[i]=(unsigned char*)ucbuf;
			}else{
				break;
			}
			w >>= 1;
			h >>= 1;
		}
	}
}
